package Parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import DataModel.Feature;
import DataModel.LociHolder;
import DataModel.Locus;
import DataModel.FeatureBucket;
import DataModel.Tupel;
import Interfaces.SortableElements;
import SortSearch.QuickSorter;

/**
 * This class implements the parser of an EMBL file and creates a
 * {@link LociHolder}. If its a multi embl file the {@link LociHolder} contains
 * several {@link Locus} in its list.
 * 
 * @author Stefan Lorenz <br>
 *         <b>Date:</b> 12.11.2012
 */
public class ParseEmbl {
	/**
	 * List where all locis are stored
	 */
	private LociHolder lociHolder;

	/**
	 * Constructor of the embl parser
	 * 
	 * @param emblFile
	 *            Embl file to parse
	 * @throws IOException
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public ParseEmbl(File emblFile,String reference, String range) throws IOException {
		Tupel positions = getRangeToParseFromString(range);
		if(positions.getStart() == -1 && positions.getStop() == -1){
			positions.setStart(Integer.MIN_VALUE);
			positions.setStop(Integer.MAX_VALUE);
		}
		BufferedReader reader = new BufferedReader(new FileReader(emblFile));

		// no name counter
		int noNameCounter = 1;
		// create an empty lociholder
		this.lociHolder = new LociHolder();
		this.lociHolder.setFile(emblFile);
		// is the current read line
		String actualLine = "";
		// empty locus to fill
		Locus locus = new Locus();
		// current seen Feature
		Feature feature = null;

		// read genbankFile
		while ((actualLine = reader.readLine()) != null) {
			if (actualLine.startsWith("ID")) {
				locus = readLocus(actualLine);
			} else if (actualLine.startsWith("SQ")) {
				// add last feature
				feature.getNameOfAdditionalInfo();
				if (feature.getName().equals("")) {
					feature.setName("No Name " + noNameCounter);
					noNameCounter++;
				}
				boolean isIn = false;
				for (int i = 0; i < locus.getBucketList().size(); i++) {
					if (locus.getBucketList().get(i).addNewComponent(feature)) {
						isIn = true;
						break;
					}
				}
				if (!isIn) {
					locus.getBucketList().add(new FeatureBucket(feature));
				}
				// read sequence
				StringBuffer seq = new StringBuffer();
				boolean tooMuch = false;

				while (!(actualLine = reader.readLine()).contains("//")) {
					if (!tooMuch) {
						for (int i = 0; i < actualLine.length(); i++) {
							char letter = actualLine.charAt(i);
							if ((!Character.isDigit(letter)) && (letter != ' ') && (letter != '\n')) {
								try {
									seq.append(Character.toUpperCase(letter));
								} catch (OutOfMemoryError e2) {
									tooMuch = true;
									System.err.println("Sequence is too big....");
									break;
								}
							}
						}
					}
				}
				if (positions.getStart() == Integer.MIN_VALUE && positions.getStop() == Integer.MAX_VALUE)
					locus.setSequence(seq);
				else
					locus.setSequence(new StringBuffer(seq.substring(positions.getStart(), positions.getStop())));

				// Sort Features
				FeatureBucket[] tempFeature = new FeatureBucket[locus.getBucketList().size()];
				for (int i = 0; i < locus.getBucketList().size(); i++) {
					tempFeature[i] = locus.getBucketList().get(i);
				}

				QuickSorter sorter = new QuickSorter(tempFeature);
				sorter.run();
				locus.setFeatureBucket(new ArrayList(Arrays.asList(sorter.getA())));
				sorter = null;

				lociHolder.getListOfLoci().add(locus);

			}
			if (actualLine.subSequence(0, 2).equals("FT")) {
				if (!actualLine.startsWith("FT                   ")) {
					// add feature to locus
					if (feature != null) {
						feature.getNameOfAdditionalInfo();
						if (feature.getName().equals("")) {
							feature.setName("No Name " + noNameCounter);
							noNameCounter++;
						}
						boolean isIn = false;
						for (int i = 0; i < locus.getBucketList().size(); i++) {
							if (locus.getBucketList().get(i).addNewComponent(feature)) {
								isIn = true;
								break;
							}
						}
						if (!isIn && !feature.getType().contains("source") && feature.getStart() >= positions.getStart() && feature.getStart() <= positions.getStop()) {
							locus.getBucketList().add(new FeatureBucket(feature));
						}
					}
					feature = readFeature(reader, actualLine);
					feature.setLocus(locus.getLocusName());
				}
				// read additional information
				else if (actualLine.startsWith("FT                   /") && actualLine.contains("=\"")) {
					feature.getAdditionalInformation().add(readAdditionals(reader, actualLine, feature));
				}
			}
		}
		this.lociHolder.setTypeOfFile("embl");
	}

	/**
	 * Reads lines with "ID"-Tag
	 * 
	 * @param actualLine
	 *            Line to reads
	 * @return the locus
	 */
	private Locus readLocus(String actualLine) {
		Locus locus = new Locus();
		StringTokenizer st = new StringTokenizer(actualLine);
		// ID Tag
		st.nextToken();
		locus.setLocusName(st.nextToken().toString());
		st.nextToken();
		locus.setTypeOfSequence(st.nextToken().toString());
		st.nextToken();
		locus.setLocusSize(Integer.valueOf(st.nextToken()));
		return locus;
	}

	/**
	 * Fills an empty {@link Feature} with information
	 * 
	 * @param reader
	 *            Reader which reads the file
	 * @param currentLine
	 *            current line
	 * @return the parsed {@link Feature}
	 * @throws IOException
	 */
	private Feature readFeature(BufferedReader reader, String currentLine) throws IOException {

		Feature feature = new Feature();
		StringTokenizer st = new StringTokenizer(currentLine);
		st.nextToken();
		feature.setType(st.nextToken().toString());
		// strand information
		if (currentLine.contains("complement"))
			feature.setStrand("-");
		else
			feature.setStrand("+");

		// get positions
		boolean nextLine = true;
		// position
		while (nextLine) {
			// position
			String regex = "([\\d]*)([\\.][\\.][\\>]|[\\.][\\.])([\\d]*)";

			Pattern p = Pattern.compile(regex);
			Matcher m = p.matcher(currentLine);
			// suche exon positionen
			while (m.find()) {
				feature.getListOfPositions().add(new Tupel(Integer.valueOf(m.group(1)), Integer.valueOf(m.group(3))));
			}
			if (currentLine.endsWith(",")) {
				currentLine = reader.readLine();
			} else {
				nextLine = false;
			}

		}

		Tupel[] tmpTupel = new Tupel[feature.getListOfPositions().size()];
		for (int i = 0; i < tmpTupel.length; i++) {
			tmpTupel[i] = feature.getListOfPositions().get(i);
		}

		QuickSorter sorter = new QuickSorter((SortableElements[]) tmpTupel);
		sorter.run();
		ArrayList<Tupel> list = new ArrayList<Tupel>();

		for (SortableElements s : sorter.getA()) {
			list.add((Tupel) s);
		}

		feature.setListOfPositions(list);

		// set absoulte psotion of feature
		int absStart = feature.getListOfPositions().get(0).getStart();
		int absStop = feature.getListOfPositions().get(feature.getListOfPositions().size() - 1).getStop();
		feature.setAbsoultePositions(new Tupel(absStart, absStop));

		return feature;
	}

	/**
	 * Adds to a feature the additional information like dbxref, locus_tag etc.
	 * 
	 * @param reader
	 *            the reader which reads the file
	 * @param currentLine
	 * @param feature
	 *            {@link Feature} to add information to
	 * @throws IOException
	 */
	private String readAdditionals(BufferedReader reader, String currentLine, Feature feature) throws IOException {
		boolean next = true;
		String info = "";
		while (next) {
			if (currentLine.endsWith("\"")) {
				info = info.concat(currentLine.substring(21));
				next = false;
				return info;
			} else {
				while (!currentLine.endsWith("\"")) {
					info = info.concat(currentLine.substring(21));
					currentLine = reader.readLine();
					info = info.concat(currentLine.substring(21));
				}
				return info;
			}
		}
		return null;
	}

	/**
	 * Returns the {@link LociHolder}
	 * 
	 * @return the lociholder
	 */
	public LociHolder getLociHolder() {
		return lociHolder;
	}
	
	/**
	 * parses an region string which must contain either "-" or ":" as seperator
	 * of two INTs
	 * 
	 * @param region
	 *            to load into browser
	 * @return
	 */
	private Tupel getRangeToParseFromString(String region) {
		String range = region.replace(" ", "");
		Tupel tupel = new Tupel(-1, -1);
		if (range.toLowerCase() != "complete") {
			// sort order
			int start = -1;
			int stop = -1;
			if (range.contains("-")) {
				start = Integer.parseInt(range.split("-")[0]);
				stop = Integer.parseInt(range.split("-")[1]);
			} else if (range.contains(":")) {
				start = Integer.parseInt(range.split(":")[0]);
				stop = Integer.parseInt(range.split(":")[1]);
			}
			tupel.setStart(Math.min(start, stop));
			tupel.setStop(Math.max(start, stop));
		}
		return tupel;
	}
}
